/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.playground.core.service.ruler.impl;

import com.playground.core.entity.Game;
import com.playground.core.entity.LoggedPlayer;
import com.playground.core.entity.Player;
import com.playground.core.entity.boardgame.BoardGame;
import com.playground.core.entity.boardgame.Move;
import com.playground.core.entity.boardgame.Piece;
import com.playground.core.entity.boardgame.chesslikegame.ChessLikeSquare;
import com.playground.core.entity.boardgame.chesslikegame.checkers.CheckersBoard;
import com.playground.core.entity.boardgame.chesslikegame.checkers.CheckersPiece;
import com.playground.core.service.ruler.CheckersRuler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;

/**
 *
 * @author Tiago Peres
 */
@Service("checkersRuler")
public class CheckersRulerImpl extends ChessLikeGameRulerImpl implements CheckersRuler {

    @Override
    public void endTurn(BoardGame game, Move move) {
        /* if the move made was a jump and if, after the move, there's another
        jump avaiable, the player in turn continues to play. Otherwise, the
        player in turn becomes the next player. */
        if (!wasJump(move) || getAvaiableJumps(game, (ChessLikeSquare) move.getDestination()).isEmpty()){
            LoggedPlayer next = game.getPlayers().get((game.getPlayers().indexOf(game.getPlayerInTurn()) + 1) % game.getPlayers().size());
            game.setPlayerInTurn(next);
        }
    }
    
    @Override
    public boolean isMoveAllowed(BoardGame game, Move move) {
        ChessLikeSquare source = (ChessLikeSquare) move.getSource();
        ChessLikeSquare destination = (ChessLikeSquare) move.getDestination();
        CheckersPiece piece = (CheckersPiece) source.getPiece();
        
        /* the piece in the source square must be an allowed piece to be moved 
        by the player in turn and the destination square must be one of the
        squares it can be moved to */
        Map<CheckersPiece, List<ChessLikeSquare>> possibleMoves = getAllowedPieces(game, game.getPlayerInTurn());
        for (CheckersPiece p : possibleMoves.keySet()) {
            if (p.equals(piece)){
                if (possibleMoves.get(p).contains(destination)){
                    return true;
                }
            }
        }
        
        return false;
    }
    
    @Override
    // ATTENTION: THE PIECE WAS ALREADY MOVED TO ITS DESTINATION
    protected void executeReactions(BoardGame game, Move move) {
        ChessLikeSquare src = (ChessLikeSquare) move.getSource();
        ChessLikeSquare dest = (ChessLikeSquare) move.getDestination();
        // remove the jumped piece if a jump was made
        if (wasJump(move)){
            int rowDif = dest.getRow() - src.getRow();
            int colDif = dest.getColumn()- src.getColumn();
            ChessLikeSquare jumped = ((CheckersBoard) game.getBoard()).getSquareAt(src.getRow() + rowDif/2, src.getColumn() + colDif/2);
            ((CheckersBoard) game.getBoard()).removePiece(jumped.getPiece());
        }
        // If a player's 1 piece arrived at the top of the board it becomes kinged
        // dest instead of src because the piece was already moved
        if (dest.getRow() == 0 && dest.getPiece().getOwner().equals(game.getPlayers().get(1))){
            ((CheckersPiece) dest.getPiece()).setKinged(true);
        }
        // Else, if a player's 0 piece arrived at the bottom of the board it becomes kinged
        // dest instead of src because the piece was already moved
        else if (dest.getRow() == 7 && dest.getPiece().getOwner().equals(game.getPlayers().get(0))){
            ((CheckersPiece) dest.getPiece()).setKinged(true);
        }
    }

    @Override
    public boolean isGameOver(Game game) {
        BoardGame bg = (BoardGame) game;
        if (countPiecesFromPlayer(bg, game.getPlayers().get(0)) == 0){
            bg.setWinner(game.getPlayers().get(1));
            return true;
        }
        if (countPiecesFromPlayer(bg, bg.getPlayers().get(1)) == 0){
            bg.setWinner(bg.getPlayers().get(0));
            return true;
        }
        boolean noMoveForP0 = getAllowedPieces(bg, bg.getPlayers().get(0)).isEmpty();
        boolean noMoveForP1 = getAllowedPieces(bg, bg.getPlayers().get(1)).isEmpty();
        // draw: if no player can make a move
        if (noMoveForP0 && noMoveForP1){
            return true;
        }
        // win: if the player in turn cannot move any piece
        if (bg.getPlayerInTurn().equals(bg.getPlayers().get(0)) && noMoveForP0){
             bg.setWinner(bg.getPlayers().get(1));
             return true;
        }
        if (bg.getPlayerInTurn().equals(bg.getPlayers().get(1)) && noMoveForP1){
             bg.setWinner(bg.getPlayers().get(0));
             return true;
        }
        return false;
    }
    
    
    @Override
    public Map<CheckersPiece, List<ChessLikeSquare>> getAllowedPieces(BoardGame game, Player player){
        List<Piece> pieces = game.getBoard().getPieces(player);
        Map<CheckersPiece, List<ChessLikeSquare>> allowed = new HashMap<CheckersPiece, List<ChessLikeSquare>>();
        for(Piece p : pieces){
            CheckersPiece cp = (CheckersPiece) p;
            List<ChessLikeSquare> squares = getAvaiableJumps(game, (ChessLikeSquare) cp.getSquare());
            if (!squares.isEmpty()){
                allowed.put(cp, squares);
            }
        }
        if (allowed.isEmpty()){
            for(Piece p : pieces){
                CheckersPiece cp = (CheckersPiece) p;
                List<ChessLikeSquare> squares = getAvaiableSimpleMoves(game, (ChessLikeSquare) cp.getSquare());
                if (!squares.isEmpty()){
                    allowed.put(cp, squares);
                }
            }
        }
        return allowed;
    }
    
    /*******************
     * Utility methods *
     *******************/
    
    /* makes a simple verification to see if a jump was made in a given move, it
    can't verify the board, since the move was already made and the board was 
    already modified.*/
    private boolean wasJump(Move move){
        ChessLikeSquare src = (ChessLikeSquare) move.getSource();
        ChessLikeSquare dest = (ChessLikeSquare) move.getDestination();
        int rowDif = Math.abs(src.getRow() - dest.getRow());
        return rowDif > 1;
    }
    
    /* makes a deep verification to see if the move represents a real jump. It
    uses the board to make such verification. The move must not yet have
    been made. */
    /*private boolean isJump(Move move){
        ChessLikeSquare src = (ChessLikeSquare) move.getSource();
        ChessLikeSquare dest = (ChessLikeSquare) move.getDestination();
        int rowDif = dest.getRow() - src.getRow();
        int colDif = dest.getColumn()- src.getColumn();
        // the movement should consist of two sqaures in diagonal
        if (Math.abs(rowDif) != 2 || Math.abs(colDif) != 2) return false;
        // the jumped square must have a piece of the enemy player
        ChessLikeSquare jumped = ((CheckersBoard) getBoard()).getSquareAt(src.getRow() + rowDif/2, src.getColumn() + colDif/2);
        if (jumped.isFree() || jumped.getPiece().getOwner().equals(src.getPiece().getOwner())) return false;
        return true;
    }*/
    
     /* returns the target squares of the avaiable jumps starting at the 
     specified square. */
    private List<ChessLikeSquare> getAvaiableJumps(BoardGame game, ChessLikeSquare from){
        List<ChessLikeSquare> squares = new ArrayList<ChessLikeSquare>();
        CheckersBoard board = (CheckersBoard) game.getBoard();
        // if the piece is kinged
        if (((CheckersPiece) from.getPiece()).isKinged()){
            // is there a jump avaiable in any direction?
            if (isJumpAvaiable(game, from, -1, 1)) squares.add(board.getSquareAt(from.getRow() - 2, from.getColumn() + 2));
            if (isJumpAvaiable(game, from, -1, -1)) squares.add(board.getSquareAt(from.getRow() - 2, from.getColumn() - 2));
            if (isJumpAvaiable(game, from, 1, 1)) squares.add(board.getSquareAt(from.getRow() + 2, from.getColumn() + 2));
            if (isJumpAvaiable(game, from, 1, -1)) squares.add(board.getSquareAt(from.getRow() + 2, from.getColumn() - 2));
        }
        // else, if it's the player who started on top making the jump
        else if (game.getPlayers().indexOf(from.getPiece().getOwner()) == 0){
            // is there a jump avaiable in directions se or sw?
            if (isJumpAvaiable(game, from, 1, 1)) squares.add(board.getSquareAt(from.getRow() + 2, from.getColumn() + 2));
            if (isJumpAvaiable(game, from, 1, -1)) squares.add(board.getSquareAt(from.getRow() + 2, from.getColumn() - 2));
        }
        // if it's the player who started at bottom making the jump
        else{
            // is there any jump avaiable in directions ne or nw?
            if (isJumpAvaiable(game, from, -1, 1)) squares.add(board.getSquareAt(from.getRow() - 2, from.getColumn() + 2));
            if (isJumpAvaiable(game, from, -1, -1)) squares.add(board.getSquareAt(from.getRow() - 2, from.getColumn() - 2));
        }
        return squares;
    }
    
    private boolean isJumpAvaiable(BoardGame game, ChessLikeSquare from, int directionRow, int directionCol){       
        if ((from.getRow() <= 1 && directionRow == -1) ||
            (from.getRow() >= 6 && directionRow == 1) ||
            (from.getColumn() <= 1 && directionCol == -1) ||
            (from.getColumn() >= 6 && directionCol == 1)) {
            return false;
        }
        
        ChessLikeSquare sq1 = ((CheckersBoard) game.getBoard()).getSquareAt(from.getRow() + directionRow, from.getColumn() + directionCol);
        ChessLikeSquare sq2 = ((CheckersBoard) game.getBoard()).getSquareAt(from.getRow() + directionRow * 2, from.getColumn() + directionCol * 2);
        return !sq1.isFree() && !sq1.getPiece().getOwner().equals(from.getPiece().getOwner()) && sq2.isFree();
    }
    
    // get all simple moves avaiable for a given piece. "simple" means: no jump.
    private List<ChessLikeSquare> getAvaiableSimpleMoves(BoardGame game, ChessLikeSquare from){
        List<ChessLikeSquare> avaiable = new ArrayList<ChessLikeSquare>();
        int row = from.getRow();
        int col = from.getColumn();
        CheckersPiece piece = (CheckersPiece) from.getPiece();
        CheckersBoard board = (CheckersBoard) game.getBoard();
        
        // is nw movement allowed
        if ((row - 1) >= 0 && (col - 1) >= 0){
            ChessLikeSquare sq = board.getSquareAt(row - 1, col - 1);
            // just kinged pieces and pieces from player 1 can move north
            if (sq.isFree() && (piece.isKinged() || piece.getOwner().equals(game.getPlayers().get(1)))){
                avaiable.add(sq);
            }
        }
        
        // is ne movement allowed
        if ((row - 1) >= 0 && (col + 1) < 8){
            ChessLikeSquare sq = board.getSquareAt(row - 1, col + 1);
            // just kinged pieces and pieces from player 1 can move north
            if (sq.isFree() && (piece.isKinged() || piece.getOwner().equals(game.getPlayers().get(1)))){
                avaiable.add(sq);
            }
        }
        
        // is sw movement allowed
        if ((row + 1) < 8 && (col - 1) >= 0){
            ChessLikeSquare sq = board.getSquareAt(row + 1, col - 1);
            // just kinged pieces and pieces from player 0 can move south
            if (sq.isFree() && (piece.isKinged() || piece.getOwner().equals(game.getPlayers().get(0)))){
                avaiable.add(sq);
            }
        }
        
        // is se movement allowed
        if ((row + 1) < 8 && (col + 1) < 8){
            ChessLikeSquare sq = board.getSquareAt(row + 1, col + 1);
            // just kinged pieces and pieces from player 0 can move south
            if (sq.isFree() && (piece.isKinged() || piece.getOwner().equals(game.getPlayers().get(0)))){
                avaiable.add(sq);
            }
        }
        
        return avaiable;
    }

}
